﻿/* ==============================================================================
*
*   Copyright (c) 2010, Pierre Favier
*   All rights reserved.
*
*   NTasking is free software: you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation.
*
*   Redistribution and use in source and binary forms, with or without
*   modification, are permitted provided that the following conditions
*   are met:
*
*   -   Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*   -   Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials
*       provided with the distribution.
*
*   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
*   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
*   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
*   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
*   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
*   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
*   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
*   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
*   THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*   SUCH DAMAGE.
*
* ============================================================================== */

using System;

namespace NTasking.Internals
{

    /// <summary>
    /// The <see cref="Timeout"/> class allows to define delay elements of the Ada syntax
    /// such as delay statements within selective accept statements and delay alternatives
    /// within selective call syntax
    /// </summary>
    internal class Timeout : IContinuable
    {

        /// <summary>
        /// Define the type of a <see cref="Timeout"/>
        /// </summary>
        internal enum TimeoutKind
        {
            /// <summary>
            /// The <see cref="Timeout"/> is infinite (wait forever)
            /// </summary>
            Infinite,
            /// <summary>
            /// The <see cref="Timeout"/> is relative (wait for an amount of time)
            /// </summary>
            Relative,
            /// <summary>
            /// The <see cref="Timeout"/> is absolute (wait until a specified date/time)
            /// </summary>
            Absolute
        }


        #region Static Fields

        /// <summary>
        /// A unique instance of the <see cref="Timeout"/> fr specifying infinite timeouts
        /// </summary>
        internal static readonly Timeout Infinite = new Timeout();

        #endregion


        #region Properties

        /// <summary>
        /// Gets or sets the barrier associated with this instance (which will permit to
        /// determine whether this instance is "open")
        /// </summary>
        /// <value>The barrier.</value>
        internal Func<bool> Barrier { get; set; }


        /// <summary>
        /// Gets or sets the relative value of this instance.
        /// </summary>
        /// <value>The relative value.</value>
        private TimeSpan RelativeValue { get; set; }


        /// <summary>
        /// Gets or sets the absolute value of this instance.
        /// </summary>
        /// <value>The absolute value.</value>
        private DateTime AbsoluteValue { get; set; }


        /// <summary>
        /// Gets or sets the kind of this instance.
        /// </summary>
        /// <value>The kind.</value>
        private TimeoutKind Kind { get; set; }


        /// <summary>
        /// Gets or sets the final value of this instance, once it has been frozen.
        /// </summary>
        /// <value>The final value.</value>
        internal DateTime Value { get; private set; }


        /// <summary>
        /// Gets or Sets a delegate representing the sequence of statements associated to this instance.
        /// </summary>
        /// <value>
        /// A delegate representing the sequence of statements associated to this instance.
        /// </value>
        public Action Continuation { get; set; }

        #endregion


        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Timeout"/> class.
        /// </summary>
        private Timeout()
        {
            Kind = TimeoutKind.Infinite;
        }


        /// <summary>
        /// Initializes a new absolute instance of the <see cref="Timeout"/> class.
        /// </summary>
        /// <param name="until">The until.</param>
        internal Timeout(DateTime until)
        {
            AbsoluteValue = until;
            Kind = TimeoutKind.Absolute;
        }


        /// <summary>
        /// Initializes a new relative instance of the <see cref="Timeout"/> class.
        /// </summary>
        /// <param name="duration">The duration.</param>
        internal Timeout(TimeSpan duration)
        {
            RelativeValue = duration;
            Kind = TimeoutKind.Relative;
        }


        /// <summary>
        /// Initializes a new relatve instance of the <see cref="Timeout"/> class.
        /// </summary>
        /// <param name="milliseconds">The duration milliseconds.</param>
        internal Timeout(int milliseconds)
        {
            RelativeValue = TimeSpan.FromMilliseconds(milliseconds);
            Kind = TimeoutKind.Relative;
        }


        /// <summary>
        /// Initializes a new instance of the <see cref="Timeout"/> class.
        /// </summary>
        /// <param name="seconds">The duration in seconds.</param>
        internal Timeout(double seconds)
        {
            RelativeValue = TimeSpan.FromSeconds(seconds);
            Kind = TimeoutKind.Relative;
        }

        #endregion


        #region Methods

        /// <summary>
        /// Freezes this instance, i.e. fixes the Value property.
        /// </summary>
        internal void Freeze()
        {
            switch (Kind)
            {
                case TimeoutKind.Absolute:
                    Value = AbsoluteValue;
                    break;
                case TimeoutKind.Relative:
                    Value = DateTime.Now + RelativeValue;
                    break;
            }
        }


        /// <summary>
        /// Determines whether this instance is opened, i.e. it has no barrier or it's barrier is true.
        /// </summary>
        /// <returns>
        /// 	<c>true</c> if this instance is opened; otherwise, <c>false</c>.
        /// </returns>
        internal bool IsOpened()
        {
            return Barrier == null || Barrier();
        }


        /// <summary>
        /// Executes the continuation sequence of statements.
        /// </summary>
        public void Continue()
        {
            if (Continuation != null)
                Continuation();
        }


        /// <summary>
        /// Determines whether the specified <see cref="Timeout"/> is later than this instance.
        /// </summary>
        /// <param name="other">The other <see cref="Timeout"/> to compare.</param>
        /// <returns>
        /// 	<c>true</c> if the specified <see cref="Timeout"/> is later; otherwise, <c>false</c>.
        /// </returns>
        public bool IsLater(Timeout other)
        {
            if (other.Kind == TimeoutKind.Infinite)
                return false;

            switch (Kind)
            {
                case TimeoutKind.Relative:
                    return other.Kind == TimeoutKind.Relative && RelativeValue > other.RelativeValue;
                case TimeoutKind.Absolute:
                    return other.Kind == TimeoutKind.Absolute && AbsoluteValue > other.AbsoluteValue;
                default:
                    return true;
            }
        }

        #endregion

    }

}
